﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

namespace server.app
{
    public class Orm<T>
    {
        class Field
        {
            public bool IsKey { get; set; }
            public bool BigText { get; set; }
            public string Name { get; set; }
            public Type Type { get; set; }
        }
        static Dictionary<string, List<Field>> _fieldDic = new Dictionary<string, List<Field>>();

        Type _type = null;
        string _tableName = null;
        Field[] _fields = null;
        World _world = null;
        string _keyField = null;

        public Orm(string tableName, World world)
        {
            _world = world;
            _tableName = tableName;
            _type = typeof(T);

            #region Read Fd, Prop
            if (!_fieldDic.ContainsKey(_type.FullName))
            {
                var fieldList = new List<Field>();
                foreach (var fd in _type.GetFields())
                {
                    var fdValue = fd.GetValue(null).ToString();
                    var prop = _type.GetProperty(fdValue);
                    if (prop != null)
                    {
                        var isKey = fd.Name.IndexOf("KEY") == 0;
                        if (isKey)
                        {
                            _keyField = fdValue;
                        }
                        fieldList.Add(new Field
                        {
                            IsKey = isKey,
                            BigText = fd.Name.IndexOf("BIG_TEXT") == 0,
                            Name = fdValue,
                            Type = prop.PropertyType
                        });
                    }
                }
                _fieldDic.Add(_type.FullName, fieldList);
            }
            #endregion

            if (_keyField == null)
            {
                throw new Exception(_tableName + " must has key");
            }

            _fields = _fieldDic[_type.FullName].ToArray();
        }

        string TypeToFieldType(Field fd)
        {
            var type = fd.Type;
            if (fd.BigText)
            {
                return "text";
            }
            else if (type == typeof(int) || type == typeof(bool) || type.IsEnum)
            {
                return "int";
            }
            if (type == typeof(string))
            {
                return "varchar(100)";
            }
            else if (type == typeof(DateTime))
            {
                return "datetime";
            }
            return "text";
        }
        object ReadTo(Field fd, object value)
        {
            var type = fd.Type;
            if (type == typeof(bool))
            {
                return (int)value == 1;
            }
            else if (type == typeof(int) || type.IsEnum)
            {
                return (int)value;
            }
            else if (type == typeof(string))
            {
                return value.ToString();
            }
            else if (type == typeof(DateTime))
            {
                return (DateTime)value;
            }
            return comm.BinarySerializer.DeserializeFromBase64(value.ToString());
        }
        object WriteTo(Field fd, object value)
        {
            var type = fd.Type;
            if (type == typeof(bool) ||
                type == typeof(int) ||
                type == typeof(string) ||
                type == typeof(DateTime))
            {
                return value;
            }
            else if (type.IsEnum)
            {
                return (int)value;
            }
            return comm.BinarySerializer.SerializeToBase64(value);
        }

        public string CreateTableSql()
        {
            var sb = new StringBuilder();
            sb.Append("if object_id('" + _tableName + "') is not null drop table [" + _tableName + "];");
            sb.Append("create table [" + _tableName + "] (");
            foreach (var fd in _fields)
            {
                sb.Append("[" + fd.Name + "] " + TypeToFieldType(fd) + " " + (fd.IsKey ? "primary key," : "not null,"));
            }
            sb.Append(");");
            return sb.ToString();
        }

        public int Count(SQLx sqlx)
        {
            return (int)sqlx.ExecuteScalar("select count(*) from [" + _tableName + "]");
        }
        public int Count()
        {
            return _world.DataManager.Call<int>(sqlx =>
            {
                return Count(sqlx);
            });
        }

        public bool Insert(T value)
        {
            return _world.DataManager.Call<bool>(sqlx =>
            {
                return Insert(sqlx, value);
            });
        }
        public bool Insert(SQLx sqlx, T value)
        {
            var sb1 = new StringBuilder();
            var sb2 = new StringBuilder();
            var qsList = new List<object>();
            var type = value.GetType();
            var ix = -1;
            foreach (var fd in _fields)
            {
                ix++;
                sb1.Append("[" + fd.Name + "],");
                sb2.Append("|" + ix + "|,");
                qsList.Add(WriteTo(fd, type.GetProperty(fd.Name).GetValue(value, null)));
            }
            if (sb1.Length > 0)
            {
                sb1.Remove(sb1.Length - 1, 1);
                sb2.Remove(sb2.Length - 1, 1);
            }
            return sqlx.ExecuteNonQuery("insert into [" + _tableName + "](" + sb1.ToString() + ") values(" + sb2.ToString() + ")", qsList.ToArray()) > 0;
        }

        public bool Update(T value)
        {
            return _world.DataManager.Call<bool>(sqlx =>
            {
                return Update(sqlx, value);
            });
        }
        public bool Update(SQLx sqlx, T value)
        {
            var sb = new StringBuilder();
            var qsList = new List<object>();
            var type = value.GetType();
            var where = "";
            var ix = -1;
            foreach (var fd in _fields)
            {
                ix++;
                if (fd.IsKey)
                {
                    where = " where [" + fd.Name + "]=|" + ix + "|";
                }
                else
                {
                    sb.Append("[" + fd.Name + "]=|" + ix + "|,");
                }
                qsList.Add(WriteTo(fd, type.GetProperty(fd.Name).GetValue(value, null)));
            }
            if (sb.Length > 0)
            {
                sb.Remove(sb.Length - 1, 1);
            }
            return sqlx.ExecuteNonQuery("update [" + _tableName + "] set " + sb.ToString() + where, qsList.ToArray()) > 0;
        }

        public T GetObjectByKey(string key)
        {
            return _world.DataManager.Call<T>(sqlx =>
            {
                return GetObjectByKey(sqlx, key);
            });
        }
        public T GetObjectByKey(SQLx sqlx, string key)
        {
            return GetObject(sqlx, "select * from [" + _tableName + "] where [" + _keyField + "]=|0|", key);
        }

        public T GetObject(string sqlStr, params object[] qs)
        {
            return _world.DataManager.Call<T>(sqlx =>
            {
                return GetObject(sqlx, sqlStr, qs);
            });
        }
        public T GetObject(SQLx sqlx, string sqlStr, params object[] qs)
        {
            var dt = sqlx.FillTable(sqlStr, qs);
            if (dt.Rows.Count > 0)
            {
                var dr = dt.Rows[0];
                var obj = Activator.CreateInstance<T>();
                var type = obj.GetType();
                foreach (var fd in _fields)
                {
                    type.GetProperty(fd.Name).SetValue(obj, ReadTo(fd, dr[fd.Name]), null);
                }
                return (T)obj;
            }
            return default(T);
        }

        public T[] GetList(string sqlStr, params object[] qs)
        {
            return _world.DataManager.Call<T[]>(sqlx =>
            {
                return GetList(sqlx, sqlStr, qs);
            });
        }
        public T[] GetList(SQLx sqlx, string sqlStr, params object[] qs)
        {
            var dt = sqlx.FillTable(sqlStr, qs);
            var list = new List<T>();
            foreach (DataRow dr in dt.Rows)
            {
                var obj = Activator.CreateInstance<T>();
                var type = obj.GetType();
                foreach (var fd in _fields)
                {
                    type.GetProperty(fd.Name).SetValue(obj, ReadTo(fd, dr[fd.Name]), null);
                }
                list.Add((T)obj);
            }
            return list.ToArray();
        }
    }
}
